package com.example.gulimall.cart.web.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.common.constant.CartConstant;
import com.example.common.entity.product.SkuInfoEntity;
import com.example.common.to.cart.UserInfoTo;
import com.example.common.utils.GuLiMallThreadLocalPool;
import com.example.common.utils.R;
import com.example.common.utils.StrUtilPlus;
import com.example.common.vo.cart.CartItemVo;
import com.example.common.vo.cart.CartVo;
import com.example.gulimall.cart.feign.ProductFeignService;
import com.example.gulimall.cart.web.service.CartService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author zyred
 * @since v 0.1
 **/
@Slf4j
@Service
public class CartServiceImpl implements CartService {

    @Autowired private StringRedisTemplate redisTemplate;
    @Autowired private ThreadPoolExecutor threadPoolExecutor;
    @Autowired private ProductFeignService productFeignService;

    @Override
    public String cartList(Model model) {
        boolean login = GuLiMallThreadLocalPool.getUsers().isLogin();

        CartVo retVal = new CartVo();

        // 获取用户临时 redis 结果
        final String tempKey = this.getKey(true);
        BoundHashOperations<String, Object, Object> tempOps =
                this.redisTemplate.boundHashOps(tempKey);
        final List<CartItemVo> tempCarts = this.parseVal(tempOps.values());

        // 登录后用户
        if (!login) {

            final String loginKey = getKey(false);
            BoundHashOperations<String, Object, Object> loginOps =
                    this.cartOps(loginKey);

            // 获取登录后得购物车 redis 结果
            final List<CartItemVo> loginCarts = this.parseVal(loginOps.values());
            // 合并 loginCarts 与 empCarts
            this.mergeCarts(loginCarts, tempCarts);

            retVal.setCarts(loginCarts);
            model.addAttribute("cart", retVal);

            log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.info("登录购物车详情页内容：{}", JSONObject.toJSONString(retVal));
            log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

            CompletableFuture.runAsync(
                    () -> {

                        // 删除原始的 key 与登录后的 key，为登录后的 key 重新 set 值
                        removeCarts (tempKey);
                        removeCarts (loginKey);

                        // 异步操作需要重新获取连接，否则会出现连接断开的现象
                        BoundHashOperations<String, Object, Object> syncOps =
                                this.cartOps(loginKey);

                        for (CartItemVo vo : loginCarts) {
                            syncOps.put(String.valueOf(vo.getSkuId()), JSONObject.toJSONString(vo));
                        }

                    }, this.threadPoolExecutor
            );

            return "cartList";
        }

        retVal.setCarts(tempCarts);
        model.addAttribute("cart", retVal);

        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info("临时购物车详情页内容：{}", JSONObject.toJSONString(retVal));
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        return  "cartList";
    }

    @Override
    @SneakyThrows
    public String addItem(Long skuId, int num) {
        BoundHashOperations<String, Object, Object> ops = cartOps(this.getKey(null));

        String item = String.valueOf(ops.get(String.valueOf(skuId)));
        if (StrUtilPlus.isNotBlank(item)) {

            CartItemVo dbCart = JSONObject.parseObject(item, CartItemVo.class);
            dbCart.setCount(num);

            ops.put(String.valueOf(skuId), JSONObject.toJSONString(dbCart));

            return "redirect:http://cart.gulimall.com/getItem.html";
        }

        CartItemVo cart = new CartItemVo();

        // 查询商品基本信息
        CompletableFuture<Void> a = CompletableFuture.runAsync(
                () -> {
                    R r = this.productFeignService.getSkuInfo(skuId);
                    SkuInfoEntity skuInfo = r.getData("skuInfo", SkuInfoEntity.class);

                    cart.setSkuId(skuInfo.getSkuId());
                    cart.setTitle(skuInfo.getSkuTitle());
                    cart.setImage(skuInfo.getSkuDefaultImg());
                    cart.setPrice(skuInfo.getPrice());
                    cart.setCount(num);

                }, this.threadPoolExecutor
        );

        // 远程调用查询商品销售信息
        CompletableFuture<Void> b = CompletableFuture.runAsync(
                () -> {
                    List<String> saleAttr = this.productFeignService.getSaleAttr(skuId);
                    cart.setSkuAttr(saleAttr);
                }, this.threadPoolExecutor
        );

        // 等待两个异步线程完成
        CompletableFuture.allOf(a, b).get();

        // 把数据保存到 redis 中
        ops.put(String.valueOf(skuId), JSONObject.toJSONString(cart));
        return "redirect:http://cart.gulimall.com/getItem.html";
    }

    @Override
    public String getItem(Long skuId, Model model) {
        BoundHashOperations<String, Object, Object> ops = this.cartOps(this.getKey(null));

        CartItemVo cartItemVo = JSONObject.parseObject(String.valueOf(ops.get(String.valueOf(skuId))), CartItemVo.class);
        model.addAttribute("item", cartItemVo);
        return "success";
    }

    @Override
    public String checkItem(Long skuId, boolean checked, Model model) {

        final CartVo retVal = new CartVo();

        final String key = this.getKey(false);

        if (StrUtil.isBlank(key)) {
            return "redirect:http://cart.gulimall.com/cart.html";
        }

        BoundHashOperations<String, Object, Object> ops = this.cartOps(key);
        final String mapKey = String.valueOf(skuId);

        CartItemVo itemVo = JSONObject.parseObject(String.valueOf(ops.get(mapKey)), CartItemVo.class);
        if (Objects.isNull(itemVo)) {
            return "redirect:http://cart.gulimall.com/cart.html";
        }

        itemVo.setCheck(checked);
        CompletableFuture.runAsync(
                () -> {
                    final BoundHashOperations<String, Object, Object> cartOps = this.cartOps(key);
                    cartOps.put(mapKey, JSONObject.toJSONString(itemVo));
                }, this.threadPoolExecutor
        );
        model.addAttribute("cart", retVal.setCarts(this.parseVal(ops.values())));
        return "cartList";
    }

    @Override
    public List<CartItemVo> getCurrentUserItem(Long userId) {

        UserInfoTo users = GuLiMallThreadLocalPool.getUsers();
        log.info(">>>>>>>>>>>>>>>>>>>>>> 购物车 当前登录的用户信息 >>>>>>>>>>>>>>>>>>>>>>");
        log.info("用户信息：{}", users);
        log.info("<<<<<<<<<<<<<<<<<<<<<< 购物车 当前登录的用户信息 <<<<<<<<<<<<<<<<<<<<<<");

        String key = CartConstant.CART_REDIS_PREFIX.concat(String.valueOf(userId));
        BoundHashOperations<String, Object, Object> ops = this.cartOps(key);

        List<CartItemVo> val = this.parseVal(ops.values());

        if (CollectionUtil.isEmpty(val)) {
            return Collections.emptyList();
        }

        List<Long> skuIds = val.stream().map(CartItemVo::getSkuId).collect(Collectors.toList());
        List<SkuInfoEntity> newPriceSku = this.productFeignService.getNewPrice(skuIds);


        Map<Long, BigDecimal> skuIdMap = newPriceSku.stream().collect(Collectors.toMap(
                SkuInfoEntity::getSkuId, SkuInfoEntity::getPrice
        ));

        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>> 商品对应价格关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info(JSONObject.toJSONString(skuIdMap));
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<< 商品对应价格关系 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        List<CartItemVo> collect = val.stream()
                .filter(CartItemVo::isCheck)
                .peek(
                        a -> {
                            BigDecimal oldPrice = a.getPrice();
                            BigDecimal newPrice = skuIdMap.get(a.getSkuId());
                            if (Objects.nonNull(newPrice)) {
                                a.setPrice(oldPrice.compareTo(newPrice) != 0 ? newPrice : oldPrice);
                            }
                        }
                )
                .collect(Collectors.toList());
        log.info("当前用户 sku 属性 : {}", JSONObject.toJSONString(collect));
        return collect;
    }

    /**
     * 根据临时用户或登录用户创建不同的购物车缓存操作类
     * @return      redis hash
     */
    private BoundHashOperations<String, Object, Object> cartOps (String key) {
        return this.redisTemplate.boundHashOps(key);
    }


    /**
     * 根据条件生成 key
     *
     * @param b     可选 true(临时key) false(登录用户) null(根据登录情况选择)
     * @return      key
     */
    private String getKey (Boolean b) {
        UserInfoTo info = GuLiMallThreadLocalPool.getUsers();

        return CartConstant.CART_REDIS_PREFIX.concat(
                // 计算条件
                (b == null ? info.isLogin() : b)
                ? info.getUserKey()
                : String.valueOf(info.getUserId()));
    }

    /**
     * 解析 redis 返回得结果
     *
     * @param val       redis <Object>  -> <CartItemVo>
     * @return          <CartItemVo>
     */
    private List<CartItemVo> parseVal (List<Object> val) {
        if (CollectionUtil.isEmpty(val)) {
            return Collections.emptyList();
        }

        return val.stream().map(a ->
                    JSONObject.parseObject(String.valueOf(a), CartItemVo.class)
        ).collect(Collectors.toList());
    }

    /**
     * 合并两个结果集
     *
     * @param store     最终返回得结果，将结果存储到得目标集合
     * @param breaks    拆分结果，拆分得目标结果
     */
    private void mergeCarts (List<CartItemVo> store, List<CartItemVo> breaks){
        // 将 breaks 转换为 map ，减少时间复杂度
        Map<Long, CartItemVo> breakMap =
                breaks.stream().collect(Collectors.toMap(CartItemVo::getSkuId, Function.identity()));

        // 被使用的 key
        Set<Long> used = new HashSet<>();

        for (CartItemVo itemVo : store) {

            CartItemVo br = breakMap.get(itemVo.getSkuId());
            if (Objects.isNull(br)) { continue; }

            used.add(itemVo.getSkuId());
            itemVo.setCount((br.getCount() + itemVo.getCount()));
        }

        // 所有的 key
        Set<Long> keys = breakMap.keySet();

        for (Long all : keys) {
            if (!used.contains(all)) {
                store.add(breakMap.get(all));
            }
        }
    }

    /**
     * 移除购物车
     *
     * @param key     key
     */
    private void removeCarts(String key) {
        this.redisTemplate.delete(key);
    }
}
